/*
 *
 * Paros and its related class files.
 * 
 * Paros is an HTTP/HTTPS proxy for assessing web application security.
 * Copyright (C) 2003-2004 Chinotec Technologies Company
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Clarified Artistic License
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Clarified Artistic License for more details.
 * 
 * You should have received a copy of the Clarified Artistic License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.parosproxy.paros.extension.spider;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.SQLException;
import java.util.Vector;

import javax.swing.JMenuItem;
import javax.swing.JTree;

import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.parosproxy.paros.core.spider.Spider;
import org.parosproxy.paros.core.spider.SpiderListener;
import org.parosproxy.paros.core.spider.SpiderParam;
import org.parosproxy.paros.extension.CommandLineArgument;
import org.parosproxy.paros.extension.CommandLineListener;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.SessionChangedListener;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.model.Session;
import org.parosproxy.paros.model.SiteMap;
import org.parosproxy.paros.model.SiteNode;
import org.parosproxy.paros.network.HttpMessage;

/**
 * 
 * To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Generation - Code and Comments
 */
public class ExtensionSpider extends ExtensionAdaptor implements SpiderListener, SessionChangedListener, CommandLineListener {

	private static final int ARG_SPIDER_IDX = 0;
	private static final int ARG_URL_IDX = 1;
	private JMenuItem menuItemSpider = null;
	private SpiderDialog dialog = null;
	private Spider spider = null;
	private SiteMap siteTree = null;
	private SiteNode startNode = null;

	private PopupMenuSpider popupMenuSpider = null;
	private SpiderPanel spiderPanel = null;
	private OptionsSpiderPanel optionsSpiderPanel = null;
	private SpiderParam spiderParam = null;
	private CommandLineArgument[] arguments = new CommandLineArgument[2];
	
	// ZAP: Added logger
    private static Log log = LogFactory.getLog(ExtensionSpider.class);

	/**
     * 
     */
	public ExtensionSpider() {
		super();
		initialize();
	}

	/**
	 * @param name
	 */
	public ExtensionSpider(String name) {
		super(name);
	}

	/**
	 * This method initializes this
	 * 
	 * @return void
	 */
	private void initialize() {
		this.setName("ExtensionSpider");

	}

	/**
	 * This method initializes menuItemSpider
	 * 
	 * @return javax.swing.JMenuItem
	 */
	JMenuItem getMenuItemSpider() {
		if (menuItemSpider == null) {
			menuItemSpider = new JMenuItem();
			menuItemSpider.setText("Spider...");
			menuItemSpider.addActionListener(new ActionListener() {

						public void actionPerformed(ActionEvent e) {
							JTree siteTree = getView().getSiteTreePanel().getTreeSite();
							SiteNode node = (SiteNode) siteTree.getLastSelectedPathComponent();
							HttpMessage msg = null;
							if (node == null) {
								getView().showWarningDialog("You need to visit the website via a browser first and select a URL/folder/node in the 'Sites' panel displayed.");
								return;
							}
							setStartNode(node);
							if (node.isRoot()) {
								showDialog("All sites will be crawled");
							} else {
								try {
									msg = node.getHistoryReference().getHttpMessage();
								} catch (Exception e1) {
									// ZAP: Log exceptions
		                        	log.warn(e1.getMessage(), e1);
									return;
								}
								String tmp = msg.getRequestHeader().getURI().toString();
								showDialog(tmp);
							}

						}
					});

		}
		return menuItemSpider;
	}

	public void hook(ExtensionHook extensionHook) {
		super.hook(extensionHook);
		if (getView() != null) {
			extensionHook.getHookMenu().addAnalyseMenuItem(getMenuItemSpider());
			
			extensionHook.getHookMenu().addAnalyseMenuItem(extensionHook.getHookMenu().getMenuSeparator());
			
			extensionHook.getHookMenu().addPopupMenuItem(getPopupMenuSpider());
			extensionHook.getHookView().addStatusPanel(getSpiderPanel());
			extensionHook.getHookView().addOptionPanel(getOptionsSpiderPanel());
		}
		extensionHook.addSessionListener(this);
		extensionHook.addOptionsParamSet(getSpiderParam());
		extensionHook.addCommandLine(getCommandLineArguments());
	}

	public void startSpider() {
		siteTree = getModel().getSession().getSiteTree();

		if (startNode == null) {
			startNode = (SiteNode) siteTree.getRoot();
		}
		startSpider(startNode);

	}

	private void startSpider(SiteNode startNode) {

		if (spider == null) {
			try {
				getModel().getDb().getTableHistory().deleteHistoryType(
						getModel().getSession().getSessionId(),
						HistoryReference.TYPE_SPIDER_SEED);
				getModel().getDb().getTableHistory().deleteHistoryType(
						getModel().getSession().getSessionId(),
						HistoryReference.TYPE_SPIDER_VISITED);

			} catch (SQLException e) {
				// ZAP: Log exceptions
            	log.warn(e.getMessage(), e);
			}

			spider = new Spider(getSpiderParam(), getModel().getOptionsParam().getConnectionParam(), getModel());
			spider.addSpiderListener(this);

			inOrderSeed(spider, startNode);

		}

		getSpiderPanel().setTabFocus();

		try {
			spider.start();

		} catch (NullPointerException e1) {
			// ZAP: Log exceptions
        	log.warn(e1.getMessage(), e1);
		}
	}

	private void inOrderSeed(Spider spider, SiteNode node) {

		try {
			if (!node.isRoot()) {
				HttpMessage msg = node.getHistoryReference().getHttpMessage();
				if (msg != null) {
					if (!msg.getResponseHeader().isImage()) {
						spider.addSeed(msg);
					}
				}
			}
		} catch (Exception e) {
			// ZAP: Log exceptions
        	log.warn(e.getMessage(), e);
		}

		if (!node.isLeaf()) {
			for (int i = 0; i < node.getChildCount(); i++) {
				try {
					inOrderSeed(spider, (SiteNode) node.getChildAt(i));
				} catch (Exception e) {
					// ZAP: Log exceptions
		        	log.warn(e.getMessage(), e);
				}
			}
		}
	}

	public void spiderComplete() {

		try {
			getModel().getDb().getTableHistory().deleteHistoryType(
					getModel().getSession().getSessionId(),
					HistoryReference.TYPE_SPIDER_SEED);
			getModel().getDb().getTableHistory().deleteHistoryType(
					getModel().getSession().getSessionId(),
					HistoryReference.TYPE_SPIDER_VISITED);

		} catch (SQLException e) {
			// ZAP: Log exceptions
        	log.warn(e.getMessage(), e);
		}

		if (getView() != null) {
			getMenuItemSpider().setEnabled(true);
			getPopupMenuSpider().setEnabled(true);

		}

		try {
			Thread.sleep(3000);
		} catch (Exception e) {
		}

		if (getView() != null && dialog != null) {
			if (EventQueue.isDispatchThread()) {
				dialog.dispose();
				return;
			}
			try {
				EventQueue.invokeAndWait(new Runnable() {
					public void run() {
						dialog.dispose();
					}
				});
			} catch (Exception e) {
				// ZAP: Log exceptions
	        	log.warn(e.getMessage(), e);
			}
		}
	}

	public void foundURI(HttpMessage msg, boolean isSkip) {
		if (getView() != null) {
			if (isSkip) {
				getSpiderPanel().appendFoundButSkip(
						msg.getRequestHeader().getURI().toString() + "\n");
			} else {
				getSpiderPanel().appendFound(
						msg.getRequestHeader().getURI().toString() + "\n");
			}
		}
	}

	public void readURI(HttpMessage msg) {
		SiteMap siteTree = getModel().getSession().getSiteTree();

		HistoryReference historyRef = null;
		try {
			historyRef = new HistoryReference(getModel().getSession(),
					HistoryReference.TYPE_SPIDER, msg);
		} catch (Exception e) {
			// ZAP: Log exceptions
        	log.warn(e.getMessage(), e);
		}
		siteTree.addPath(historyRef, msg);
	}

	public Spider getSpider() {
		return spider;
	}

	public void spiderProgress(final URI uri, final int percentageComplete,
			final int numberCrawled, final int numberToCrawl) {
		String uriString = "";

		if (dialog != null) {
			if (EventQueue.isDispatchThread()) {
				dialog.getTxtNumCrawled().setText(
						Integer.toString(numberCrawled));
				dialog.getTxtOutstandingCrawl().setText(
						Integer.toString(numberToCrawl));

				dialog.getProgressBar().setValue(percentageComplete);
				uriString = "";
				if (uri != null) {
					uriString = uri.toString();
				}
				dialog.getTxtDisplay().setText(uriString);
				// dialog.getTxtDisplay().setCaretPosition(0);

				return;
			}
			try {
				EventQueue.invokeAndWait(new Runnable() {
					public void run() {
						String uriString = "";
						dialog.getTxtNumCrawled().setText(
								Integer.toString(numberCrawled));
						dialog.getTxtOutstandingCrawl().setText(
								Integer.toString(numberToCrawl));

						dialog.getProgressBar().setValue(percentageComplete);
						if (uri != null) {
							uriString = uri.toString();
						}
						dialog.getTxtDisplay().setText(uriString);
					}
				});
			} catch (Exception e) {
				// ZAP: Log exceptions
	        	log.warn(e.getMessage(), e);
			}
		}
	}

	/**
	 * @return Returns the startNode.
	 */
	public SiteNode getStartNode() {
		return startNode;
	}

	/**
	 * @param startNode
	 *            The startNode to set.
	 */
	public void setStartNode(SiteNode startNode) {
		this.startNode = startNode;
	}

	void showDialog(String msg) {
		dialog = new SpiderDialog(getView().getMainFrame(), false);
		dialog.setPlugin(ExtensionSpider.this);
		dialog.setVisible(true);
		dialog.getTxtDisplay().setText(msg);
		spider = null;

	}

	/**
	 * This method initializes popupMenuSpider
	 * 
	 * @return com.proofsecure.paros.plugin.Spider.PopupMenuSpider
	 */
	PopupMenuSpider getPopupMenuSpider() {
		if (popupMenuSpider == null) {
			popupMenuSpider = new PopupMenuSpider();

			popupMenuSpider.setExtension(this);
		}
		return popupMenuSpider;
	}

	void clear() {
		spider = null;
		System.gc();
	}

	/**
	 * This method initializes spiderPanel
	 * 
	 * @return org.parosproxy.paros.extension.spider.SpiderPanel
	 */
	private SpiderPanel getSpiderPanel() {
		if (spiderPanel == null) {
			spiderPanel = new SpiderPanel();
		}
		return spiderPanel;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.parosproxy.paros.extension.SessionChangedListener#sessionChanged(
	 * org.parosproxy.paros.model.Session)
	 */
	public void sessionChanged(Session session) {
		getSpiderPanel().clear();

	}

	/**
	 * This method initializes optionsSpiderPanel
	 * 
	 * @return org.parosproxy.paros.extension.spider.OptionsSpiderPanel
	 */
	private OptionsSpiderPanel getOptionsSpiderPanel() {
		if (optionsSpiderPanel == null) {
			optionsSpiderPanel = new OptionsSpiderPanel();
		}
		return optionsSpiderPanel;
	}

	/**
	 * This method initializes spiderParam
	 * 
	 * @return org.parosproxy.paros.core.spider.SpiderParam
	 */
	private SpiderParam getSpiderParam() {
		if (spiderParam == null) {
			spiderParam = new SpiderParam();
		}
		return spiderParam;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.parosproxy.paros.extension.CommandLineListener#execute(org.parosproxy
	 * .paros.extension.CommandLineArgument[])
	 */
	public void execute(CommandLineArgument[] args) {
		String uri = null;

		if (!arguments[ARG_URL_IDX].isEnabled()
				&& (arguments[ARG_SPIDER_IDX].isEnabled())) {
			return;
		}

		spider = new Spider(getSpiderParam(), getModel().getOptionsParam()
				.getConnectionParam(), getModel());
		spider.addSpiderListener(this);

		if (arguments[ARG_URL_IDX].isEnabled()) {
			Vector<String> v = arguments[ARG_URL_IDX].getArguments();
			for (int i = 0; i < v.size(); i++) {
				uri = v.get(i);
				try {
					System.out.println("Adding seed " + uri);
					spider.addSeed(new URI(uri, true));
				} catch (URIException e) {
					// ZAP: Log exceptions
		        	log.warn(e.getMessage(), e);
				}
			}
		}

		if (arguments[ARG_SPIDER_IDX].isEnabled()) {
			System.out.println("Starting spider...");
			spider.start();
		}

		while (!spider.isStop()) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// ZAP: Log exceptions
	        	log.warn(e.getMessage(), e);
			}
		}
		System.out.println("Spider completed.");

	}

	private CommandLineArgument[] getCommandLineArguments() {
		arguments[ARG_SPIDER_IDX] = new CommandLineArgument("-spider", 0, null,
				"", "-spider : run spider.  See other parameters");
		arguments[ARG_URL_IDX] = new CommandLineArgument("-seed", -1,
				"https{0,1}://\\S+", "Seed should be a URL",
				"-seed {URL1} {URL2} ... : Add seeds to the spider for crawling.");
		return arguments;
	}

} // @jve:decl-index=0:
